package com.sebscape.sebcms.security.actions;

import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.commons.validator.EmailValidator;
import org.apache.struts2.ServletActionContext;

import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.KeyFactory;
import com.opensymphony.xwork2.ActionSupport;
import com.sebscape.sebcms.Constants;
import com.sebscape.sebcms.email.EmailServices;
import com.sebscape.sebcms.persistence.dao.GenericDao;
import com.sebscape.sebcms.security.domain.Demographic;
import com.sebscape.sebcms.security.domain.User;
import com.sebscape.sebcms.security.persistence.UserDao;
import com.sebscape.sebcms.setup.services.SiteConfigurationServices;
import com.sebscape.sebcms.tools.Utils;
import com.sebscape.sebcms.tools.VelocityTools;

public class UserActions extends ActionSupport {

	private static final long serialVersionUID = 1L;
	private static Log log = LogFactory.getLog(UserActions.class);
    
	private User user = new User();
	
	// transient vars
	private String verifyPassword;
	
	public void setVerifyPassword( String verifyPassword ){
		this.verifyPassword = verifyPassword;
	}
	
	/**
	 * @return the user
	 */
	public User getUser() {
		return user;
	}


	/**
	 * @param user the user to set
	 */
	public void setUser(User user) {
		this.user = user;
	}
	
	/**
	 * The groupIdList will be a List of Long values. The entries need to be converted
	 * to keys and added to the groupKeySet.
	 * @param groupIdList
	 */
	public void setGroupIdList( List<Long> groupIdList ){
		this.user.setGroupKeySet( new HashSet<Key>() );
		log.debug("setGroupIdList called..." );
		for( Long id: groupIdList ){
			Key key = KeyFactory.createKey( "Group", id);
			log.debug("adding key: " + id + " as " + key );
			this.user.getGroupKeySet().add( key );
		}
	}

	/**
	 * The roleIdList will be a List of Long values. The entries need to be converted
	 * to keys and added to the roleKeySet.
	 * @param groupIdList
	 */
	public void setRoleIdList( List<Long> roleIdList ){
		this.user.setRoleKeySet( new HashSet<Key>() );
		for( Long id: roleIdList ){
			this.user.getRoleKeySet().add( KeyFactory.createKey( "Role", id) );
		}
	}
	
	/////////////////////////////////////////////////////////
	// list/view actions
    ////////////////////////////////////////////////////////

	/**
	 * simple constructor.
	 */
	public UserActions() {
		super();
		this.user = new User();
		this.user.setDemographic( new Demographic() );
	}


	/**
	 * List the User objects. Supports paging by checking the request for the following parameters: <br/>
	 * <br/>
	 * Constants.PARAM_LIST_OFFSET (-1 for all)<br/>
	 * Constants.PARAM_LIST_LIMIT (-1 for all)<br/>
	 * Constants.PARAM_SORT_COLUMN (default "lastname")<br/>
	 * Constants.PARAM_SORT_ORDER (default "ascending")<br/>
	 */
	public String list(){
		log.debug("list begin...");
        Integer offset = Integer.valueOf( Utils.getRequestValue( ServletActionContext.getRequest(),
                Constants.PARAM_LIST_OFFSET, "-1" ) );
        Integer limit = Integer.valueOf( Utils.getRequestValue( ServletActionContext.getRequest(),
                Constants.PARAM_LIST_LIMIT, "-1" ) );
        HttpServletRequest request = ServletActionContext.getRequest();
		String sortColumn = Utils.getRequestValue(request,
                Constants.PARAM_SORT_COLUMN, "lastname");
        String sortOrder = Utils.getRequestValue(request,
        		Constants.PARAM_SORT_ORDER, "ascending");
		log.debug("sortColumn = " + sortColumn);
		log.debug("Creting dao.");
		GenericDao<User, Long> dao = new GenericDao<User,Long>( User.class );
		log.debug("Fetching list");
		List<User> list = dao.findAllPaged(sortColumn + " " + sortOrder, offset, limit );
		log.debug( "loaded list " + list != null? "size: " + list.size() : "is null" );
		request.setAttribute( Constants.ATTRIB_USER_LIST, list );
		return SUCCESS;
	}
	
	/**
	 * Finds userid as a request parameter, request-attribute, or in the session (in that order) identified
	 * as Constants.PARAM_USER_ID, then loads that User from the DB.
	 * @return
	 */
	public String load(){
        String userId = Utils.getRequestValue( ServletActionContext.getRequest(),
                                Constants.PARAM_USER_ID, null );
        UserDao dao = new UserDao();
        try {
            User user = dao.load( Long.valueOf( userId ) );
            ServletActionContext.getRequest().setAttribute(Constants.ATTRIB_USER, user);
            log.debug("loadUser completed; returning SUCCESS.");
            return SUCCESS;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return ERROR;
    }

    /**
     * Load a User by the username.
     * @param username
     * @return
     */
    public User load( String username ){
        UserDao dao = new UserDao();
        User user = null;
        try {
            user = dao.findByFilter( "username=" + username ).get( 0 );
        } catch( Exception e){
            log.error( e );
        }
        return user;
    }

    ///////////////////////
    // Persist actions
    ///////////////////////
    
    /**
     * Save this user Object. NOT used to update a record.
     */
    public String save(){
    	log.debug("User: " + user );
    	log.debug("username: " + user.getUsername() );
    	log.debug("password: " + user.getPassword() );
    	if( user != null && !Utils.isEmpty( user.getUsername() )
    		&& !Utils.isEmpty( user.getPassword() ) 
       	){
	    	UserDao dao = new UserDao();
	    	try {
	    		if( user.getGroupKeySet() == null ){
	    			log.debug("AT SAVE TIME...the groupKeySet: NULL" );
	    		} else {
	    			log.debug("AT SAVE TIME...the groupKeySet: " + user.getGroupKeySet().size());	    			
	    		}
	    		User newUser = dao.save( user );
	    		ServletActionContext.getRequest().setAttribute(Constants.ATTRIB_USER, newUser);
	    		log.debug("save user completed....returning SUCCESS.");
	    		return SUCCESS;
	    	} catch (Exception e) {
	    		e.printStackTrace();
	    	}
	    	return ERROR;
    	}
    	return "RETRY";
    }

    /**
     * Update the record. Requires the username.
     * @return
     */
    public String update(){
    	log.debug("User: " + user );
    	log.debug("userId: " + user.getId() );
    	log.debug("username: " + user.getUsername() );
    	log.debug("password: " + user.getPassword() );
    	if( user != null
			&& !Utils.isEmpty( user.getUsername() ) 
			&& ( 
				!Utils.isEmpty( user.getId() ) // if this is an existing record then the password is not required.
				|| !Utils.isEmpty( user.getPassword() ) // else we check for the password.
			) 
    	){
    		UserDao dao = new UserDao();
    		try {
    			// for debugging.
    			if( log.isDebugEnabled() ){
	    			if( user.getGroupKeySet() == null ){
		    			log.debug("AT SAVE TIME...the groupKeySet: NULL" );
		    		} else {
		    			log.debug("AT SAVE TIME...the groupKeySet: " + user.getGroupKeySet().size());	    			
		    		}
    			}
    			// call the dao to update the user.
    			User newUser = dao.update( user );
    			// put the updated User in the request scope.
    			ServletActionContext.getRequest().setAttribute(Constants.ATTRIB_USER, newUser);
    			log.debug("update user completed...returning SUCCESS.");
    			return SUCCESS;
    		} catch (Exception e) {
    			e.printStackTrace();
    			this.errorMap.put("UPDATE_ERROR", e.getMessage() );
        		return ERROR;
    		}
    	}
    	this.errorMap.put("MISSING_PARAMS_ERROR", "Please be sure you provided a valid username (and password if this is a new record.)" );
    	return "ERROR";
    }

	/**
	 * Delete the User
	 * @return
	 */
	public String delete(){
		UserDao dao = new UserDao();
		try {
			Long id = Long.valueOf( (String)ServletActionContext.getRequest().getParameter( "id" ) );
			log.debug("deleting user with id = " + id );
			dao.delete( id );
		} catch (Exception e) {
			e.printStackTrace();
		}
		return SUCCESS;
	}
	
    ///////////////////////
    // Registration actions
    ///////////////////////
    private Map<String,Object> errorMap = new HashMap<String,Object>();
    
    public Boolean isUnique( String username ){
    	UserDao dao = new UserDao();
    	return dao.findByUsername( username ) == null;
    }
    
    public Boolean allFieldsPresent(){
    	return (
    		!Utils.isEmpty(user.getUsername() ) &&
    		!Utils.isEmpty(user.getPassword() ) &&
    		!Utils.isEmpty(user.getDemographic().getEmailAddress() ) &&
    		!Utils.isEmpty(user.getDemographic().getFirstname() ) &&
    		!Utils.isEmpty(user.getDemographic().getLastname() )
    	);
    }
    
    public Boolean isValidEmailFormat( String email ){
    	return EmailValidator.getInstance().isValid(email);
    }
    
    public Boolean isValidPassword( String password ){
    	return !Utils.isEmpty( password ) && password.length() >= 6;
    }
    
    public Boolean isPasswordMatch(){
    	return this.user.getPassword().equals( this.verifyPassword );
    }
    
    public String register(){
    	Boolean isError = Boolean.FALSE;
    	// all fields entered?
    	if( !allFieldsPresent() ){
    		errorMap.put("REQUIRED_FIELDS_MISSING", "Please provide all required information." );
    		isError = Boolean.TRUE;
    	} else {
	    	// check unique username
	    	if( !isUnique( this.user.getUsername() ) ){
	    		errorMap.put("DUPLICATE_USERNAME", "The username provided is already in use." );
	    		isError = Boolean.TRUE;
	    	}
	    	if( !isPasswordMatch() ){
	    		errorMap.put("PASSWORD_MISMATCH", "Password does not match verify-password." );
	    		isError = Boolean.TRUE;
	    	}
	    	if( !isValidPassword( this.user.getEmailAddress() ) ){
	    		errorMap.put("INVALID_PASSWORD", "Password must be at least 6 characters." );
	    		isError = Boolean.TRUE;
	    	}
	    	if( !isValidEmailFormat( this.user.getEmailAddress() ) ){
	    		errorMap.put("INVALID_EMAIL_FORMAT", "The format of this email address is not valid." );
	    		isError = Boolean.TRUE;
	    	}
    	}
    	if( isError ){
    		ServletActionContext.getRequest().setAttribute( Constants.ATTRIB_ERROR_MAP, errorMap );
    		return ERROR;
    	} else {
    		this.user.setCreationDate( new Date() );
        	// generate a verification-code.
        	this.user.setStatusCode( Constants.USER_STATUS_PENDING_CONFIRMATION );
        	// email the user.
        	this.emailUserWelcome();
        	// save the user and done.
        	String ret = this.save();
        	// save puts the user in the request which would make them logged in, so remove the attrib.
        	ServletActionContext.getRequest().removeAttribute( Constants.ATTRIB_USER );
        	return ret;
    	}
   	}
    
    private Boolean emailUserWelcome(){
    	EmailServices service = new EmailServices( ServletActionContext.getServletContext() );
    	String template = SiteConfigurationServices.findConfiguration( ServletActionContext.getServletContext() )
    						.getEmailTemplateWelcomeUser();
    	try {
    		VelocityTools vt = new VelocityTools();
			String msgBody = vt.merge(template, this.userDetailsAsMap(), "EmailWelcome");
			service.sendMessage(this.user.getEmailAddress(), (String)null, (String)null, (String)null,// null cc, bcc, and from (uses setupconfig value) 
					"Welcome new user!", msgBody );
		} catch (Exception e) {
			e.printStackTrace();
		}
    	return Boolean.TRUE;
    }
    
    public Map<String,Object> userDetailsAsMap(){
    	Map<String,Object> valueMap = new HashMap<String,Object>();
    	valueMap.put("user", user );
    	return valueMap;
    }
    
    public String confirmUser(){
    	return SUCCESS;
    }


}
